//
//  WebSocketManager.swift
//  EyeVisionForPatients
//
//  Created by PMC on 2020/2/10.
//  Copyright © 2020 tao Tao. All rights reserved.
//

import UIKit
import Starscream
import Alamofire

private let reconnectMaxTime: TimeInterval = 64 // 设置最大重连次数
private let reconnectIntervalTime: TimeInterval = 30 // 设置重连时间间隔(秒)

class WebSocketManager: NSObject, WebSocketDelegate {
    
    // MARK: - 单例
    
    private static var _shared: WebSocketManager?
    
    class func shared() -> WebSocketManager {
        guard let instance = _shared else {
            _shared = WebSocketManager()
            return _shared!
        }
        return instance
    }
    
    /// 销毁单例
    class func destroy() {
        _shared?.disconnect()
        _shared = nil
    }
    
    // MARK: - 属性
    
    /// 网络状态管理
    private var reachabilityManager = NetworkReachabilityManager(host: "www.baidu.com")
    
    private var wsUrl: String { return ApiUrl.wsUrl }
    
    private var socket: WebSocket?
    
    /// 是否在断开连接后自动重连
    var autoReconnect = true
    /// socket是否连接
    private var isConnected = false
    /// 心跳包定时发送计时器
    private var heartbeat: Timer?
    private var heartbeatTime: Int = 0
    /// 重连计数器
    private var reconnectTime: TimeInterval = 0
    
    
    private var waitSendMsgs = [ChatMsgModel]()
    private var reSendMsgTime: Int = 0
    private var resendMsgTimer: Timer?
    
    // MARK: - FUNCTION
    
    /// 发送消息
    func sendMessage(_ msgModel: ChatMsgModel) {
        YSGLog("发送的消息: \(msgModel)")
        if let jsonStr = msgModel.toJSONString() {
            waitSendMsgs.append(msgModel)
            socket?.write(string: jsonStr)
            initResendMsgTimer()
        } else {
            HUDTool.show(message: "发送消息失败")
        }
    }
    
    /// 发送视频忙碌消息
    func sendMessageForVideo(cmd: Int, msgId: String, diagnoseId: String, from: Int, to: String) {
        let paras: [String: Any] = [
            "cmd": cmd,
            "msgId": msgId,
            "diagnoseId": diagnoseId,
            "from": from,
            "to": to
        ]
        guard let data = try? JSONSerialization.data(withJSONObject: paras),
            let jsonStr = String(data: data, encoding: .utf8) else { return }
        socket?.write(string: jsonStr)
    }
    
    /// 拉取离线消息
    private func pullOfflineMsg() {
        let paras: [String: Any] = ["cmd": 19, "type": "0", "userId": UserDefaults.userId]
        guard let data = try? JSONSerialization.data(withJSONObject: paras),
            let jsonString = String(data: data, encoding: .utf8) else { return }
        socket?.write(string: jsonString)
    }
    
    /// 收到消息后给后台回复
    func sendMsgToServe(_ msgId: String) {
        let paras: [String: Any] = ["cmd": 21, "msgId": msgId]
        guard let data = try? JSONSerialization.data(withJSONObject: paras),
            let jsonString = String(data: data, encoding: .utf8) else { return }
        socket?.write(string: jsonString)
    }
    
    /// 已读消息给后台回复
    func readMsgToServer(_ vm: AdvisoryDetailViewModel, model: ReceiveModel.MsgModel) {
        let paras: [String: Any] = [
            "cmd": 24,
            "msgId": model.id ?? "",
            "diagnoseId": vm.diagnoseId,
            "to": model.to
        ]
        guard let data = try? JSONSerialization.data(withJSONObject: paras),
            let jsonString = String(data: data, encoding: .utf8) else { return }
        socket?.write(string: jsonString)
    }
    
    /// socket已连接
    private func webSocketConnected() {
        isConnected = true
        initHeartbeat()
        
        if !waitSendMsgs.isEmpty {
            initResendMsgTimer()
        }
        
        /// 拉取离线消息
        pullOfflineMsg()
    }
    
    private func webSocketDisconnected() {
        isConnected = false
        reconnect()
    }

    
    // MARK: - WebSocketDelegate
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        switch event {
        case .connected(let headers):
            YSGLog("websocket is connected: \(headers)")
            webSocketConnected()
        case .disconnected(let reason, let code):
            YSGLog("websocket is disconnected: \(reason) with code: \(code)")
            webSocketDisconnected()
        case .text(let string):
            YSGLog("接收的消息: \(string)")
            if let model = ReceiveModel.deserialize(from: string) {
                /// 服务器响应消息发送
                if model.code == 10000 {
                    reSendMsgTime = 0
                    waitSendMsgs = waitSendMsgs.filter {
                        return $0.clientId != model.clientId
                    }
                    
                    if waitSendMsgs.isEmpty {
                        destoryResendMsgTimer()
                    }
                }
                
                /// 用户不在线
                if model.code == 10001 {
                    reSendMsgTime = 0
                    waitSendMsgs.removeAll()
                    destoryResendMsgTimer()
                }
                
                /// 服务器响应心跳包, 心跳包次数置0
                if model.command == 13 { heartbeatTime = 0 }
            }
            NotificationCenter.default.post(name: NotiName.receiveMessage, object: string)
        case .binary(let data):
            YSGLog("Received data: \(data.count)")
        case .ping(_):
            break
        case .pong(_):
            break
        case .viabilityChanged(_):
            break
        case .reconnectSuggested(_):
            break
        case .cancelled:
            isConnected = false
        case .error(let error):
            isConnected = false
            handleError(error)
            reconnect()
        }
    }
    
    // MARK: - init
    override init() {
        super.init()
        
        initSocket()
        
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil)

        
        reachabilityManager?.startListening(onUpdatePerforming: { [weak self] status in
            guard let self = self else { return }
            switch status {
            case .reachable(.cellular), .reachable(.ethernetOrWiFi):
                if self.isConnected {
                    return
                }
                if self.autoReconnect {
                    self.reconnect()
                }
            default:
                self.isConnected = false
                self.disconnect()
            }
        })
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    @objc func didBecomeActive() {
        reconnect()
    }
    
    @objc func willResignActive() {
        disconnect()
    }
}

extension WebSocketManager {
    /// 连接服务器
    func connect() {
        initSocket()
        
        // 每次正常连接的时候清零重连时间
        reconnectTime = 0
    }
    
    /// 断开连接
    private func disconnect() {
        if socket != nil {
            socket?.forceDisconnect()
            socket?.delegate = nil
            socket = nil
        }
        
        isConnected = false
        reconnectTime = 0
        heartbeatTime = 0
        reSendMsgTime = 0
        destoryHeartBeat()
        destoryResendMsgTimer()
    }
    
    /// 初始化socket
    private func initSocket() {
        if socket == nil {
            let request = URLRequest(url: URL(string: wsUrl)!)
            // 设置不需要ssl证书
            socket = WebSocket(request: request, certPinner: nil)
            socket?.delegate = self
            socket?.connect()
        }
    }
    
    /// 初始化心跳包
    private func initHeartbeat() {
        self.destoryHeartBeat()
        
        self.heartbeat = Timer.scheduledTimer(withTimeInterval: reconnectIntervalTime,
                                              repeats: true,
                                              block: { [weak self] (_) in
            self?.sendHeartbeat()
        })
        RunLoop.current.add(self.heartbeat!, forMode: .common)
    }
    
    /// 取消心跳包
    private func destoryHeartBeat() {
        if self.heartbeat != nil {
            self.heartbeat?.invalidate()
            self.heartbeat = nil
        }
    }
    
    /// 发送心跳包
    private func sendHeartbeat() {
        guard let data = try? JSONSerialization.data(withJSONObject: ["cmd": 13, "hbbyte": "heartbeat"]),
            let jsonString = String(data: data, encoding: .utf8) else { return }
        heartbeatTime += 1
        if heartbeatTime > 3 { reconnect() }
        socket?.write(string: jsonString)
    }
    
    /// 重连
    private func reconnect() {
        disconnect()
        
        // 超过一分钟就不再重连 所以只会重连5次 2^5 = 64
        if reconnectTime > 64 { return }
        
        let secons = DispatchTime.now().uptimeNanoseconds + UInt64(reconnectTime) * NSEC_PER_SEC
        let time = DispatchTime(uptimeNanoseconds: secons)
        DispatchQueue.main.asyncAfter(deadline: time) {
            self.socket = nil
            
            self.initSocket()
        }
        
        // 重连时间2的指数级增长
        if reconnectTime == 0 { reconnectTime = 2 }
        
        reconnectTime *= 2
    }
    
    /// 初始化消息重发定时器
    private func initResendMsgTimer() {
        destoryResendMsgTimer()
        
        resendMsgTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [weak self] (_) in
            self?.resendMessage()
        })
        RunLoop.current.add(resendMsgTimer!, forMode: .common)
    }
    
    /// 取消消息重发定时器
    private func destoryResendMsgTimer() {
        if resendMsgTimer != nil {
            resendMsgTimer?.invalidate()
            resendMsgTimer = nil
        }
    }
    
    /// 重发消息
    private func resendMessage() {
        reSendMsgTime += 1
        if reSendMsgTime > 3 {
            reSendMsgTime = 0
            
            if !waitSendMsgs.isEmpty {
                let model = waitSendMsgs.removeFirst()
                HUDTool.show(message: "发送消息失败")
                NotificationCenter.default.post(name: NotiName.sendMessageFail, object: model)
            }
            
            return
        }
        
        guard !waitSendMsgs.isEmpty,
            let model = waitSendMsgs.first,
            let jsonStr = model.toJSONString() else { return }
        socket?.write(string: jsonStr)
    }
}

// MARK: - Helpers
private extension WebSocketManager {
    func handleError(_ error: Error?) {
        if let e = error as? WSError {
            YSGLog("websocket encountered an error---: \(e.message)\(e.type)\(e.code)")
        } else if let e = error {
            YSGLog("websocket encountered an error+++: \(e.localizedDescription)")
        } else {
            YSGLog("websocket encountered an error")
        }
    }
}
